package com.matuw.android.logger;

import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.Nullable;


import com.matuw.android.QinSdk;
import com.matuw.android.log.QLogger;

import java.io.PrintWriter;
import java.io.StringWriter;


public class DefaultLogger implements QLogger.Logger {

    public static void use() {
        QLogger.setDelegate(new DefaultLogger(0, 2));
    }

    public static void setCustomTag(String tag) {
        customTag = tag;
    }

    static String customTag = null;
    private static final String DEFAULT_TAG = QinSdk.TAG;

    /**
     * Android's max limit for a log entry is ~4076 bytes,
     * so 2048 bytes is used as chunk size since default charset
     * is UTF-8
     */
    private static final int CHUNK_SIZE = 2048;

    private static final char TOP_LEFT_CORNER = '┌';
    private static final char BOTTOM_LEFT_CORNER = '└';
    private static final char MIDDLE_CORNER = '├';
    private static final char HORIZONTAL_LINE = '│';
    private static final String DOUBLE_DIVIDER = "──────────────────────────────────────────────────────";
    private static final String SINGLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
    private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
    private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
    private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;

    private static final String UNKNOWN_SOURCE = "Unknown Source";

    private static final int MIN_STACK_OFFSET = 5;

    private final int methodOffset;
    private final int methodCount;

    public DefaultLogger(int methodOffset, int methodCount) {
        this.methodOffset = methodOffset;
        this.methodCount = methodCount;
    }

    @Override
    public void v(String msg) {
        log(Log.VERBOSE, customTag, msg, null);
    }


    @Override
    public void d(String msg) {
        log(Log.DEBUG, customTag, msg, null);
    }

    @Override
    public void i(String msg) {
        log(Log.INFO, customTag, msg, null);
    }


    @Override
    public void w(String msg) {
        log(Log.WARN, customTag, msg, null);
    }

    public void w(String msg, Throwable tr) {
        log(Log.WARN, customTag, msg, tr);
    }

    @Override
    public void e(String msg) {
        log(Log.ERROR, customTag, msg, null);
    }

    @Override
    public void e(String msg, Throwable tr) {
        log(Log.ERROR, customTag, msg, tr);
    }

    private void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable throwable) {
        if (throwable != null && message != null) {
            message += " : " + getStackTraceString(throwable);
        }

        if (throwable != null && message == null) {
            message = getStackTraceString(throwable);
        }

        if (TextUtils.isEmpty(message)) {
            message = "Empty/NULL.";
        }

        String newTag = TextUtils.isEmpty(tag) ? DEFAULT_TAG : tag;
        Log.println(priority, newTag, TOP_BORDER);
        printHeader(priority, newTag);
        //get bytes of message with system's default charset (which is UTF-8 for Android)
        byte[] bytes = message.getBytes();
        int length = bytes.length;
        if (length <= CHUNK_SIZE) {
            if (methodCount > 0) {
                Log.println(priority, newTag, MIDDLE_BORDER);
            }
            printContent(priority, newTag, message);
            Log.println(priority, newTag, BOTTOM_BORDER);
            return;
        }

        if (methodCount > 0) {
            Log.println(priority, newTag, MIDDLE_BORDER);
        }
        for (int i = 0; i < length; i += CHUNK_SIZE) {
            int count = Math.min(length - i, CHUNK_SIZE);
            String chunk = new String(bytes, i, count);
            int chunkLength = chunk.getBytes().length;
            if (chunkLength == count) {
                printContent(priority, newTag, chunk);
            } else {
                // 修复字符串截断的位置在字节中间
                int offset = chunkLength - count;
                printContent(priority, newTag, new String(bytes, i, count + offset));
                i += offset;
            }
        }
        Log.println(priority, newTag, BOTTOM_BORDER);
    }

    private void printHeader(int priority, String tag) {
        // 头部
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        int stackOffset = getStackOffset(trace) + methodOffset;
        int methodCount;
        if (this.methodCount + stackOffset > trace.length) {
            methodCount = trace.length - stackOffset - 1;
        } else {
            methodCount = this.methodCount;
        }

        StringBuilder level = new StringBuilder();

        for (int i = methodCount; i > 0; i--) {
            int stackIndex = i + stackOffset;
            if (stackIndex >= trace.length) {
                continue;
            }
            StackTraceElement element = trace[stackIndex];
            String name = element.getClassName();
            int lastIndex = name.lastIndexOf(".");
            String className = name.substring(lastIndex + 1);
            StringBuilder builder = new StringBuilder(HORIZONTAL_LINE)
                    .append(' ')
                    .append(level)
                    .append(className)
                    .append('.')
                    .append(element.getMethodName())
                    .append("(");

            String fileName = element.getFileName();
            if (TextUtils.isEmpty(fileName)) {
                builder.append(UNKNOWN_SOURCE);
            } else {
                builder.append(fileName);
            }
            builder.append(":")
                    .append(element.getLineNumber());

            builder.append(')');

            level.append("   ");
            Log.println(priority, tag, builder.toString());
        }
    }

    private void printContent(int priority, @Nullable String tag, String chunk) {
        String[] lines = chunk.split(System.lineSeparator());
        for (String line : lines) {
            Log.println(priority, tag, HORIZONTAL_LINE + " " + line);
        }
    }

    private int getStackOffset(StackTraceElement[] trace) {
        String name1 = DefaultLogger.class.getName();
        String name2 = QLogger.class.getName();

        for (int i = MIN_STACK_OFFSET; i < trace.length; i++) {
            StackTraceElement e = trace[i];
            String name = e.getClassName();
            if (!name.equals(name1) && !name.equals(name2)) {
                return --i;
            }
        }
        return -1;
    }

    String getStackTraceString(Throwable tr) {
        if (tr == null) {
            return "";
        }

        Throwable t = tr;
        while (t != null) {
            t = t.getCause();
        }

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        tr.printStackTrace(pw);
        pw.flush();
        return sw.toString();
    }

}
